import {
  formatRelevantDigits,
  bytesToKiB,
  bytesToMiB,
  bytesToGiB,
  numberToHumanSizeSplit,
  numberToHumanSize,
  numberToMetricPrefix,
  sum,
  isOdd,
  median,
  changeInPercent,
  formattedChangeInPercent,
  isNumeric,
  isPositiveInteger,
} from '~/lib/utils/number_utils';
import {
  BYTES_FORMAT_BYTES,
  BYTES_FORMAT_KIB,
  BYTES_FORMAT_MIB,
  BYTES_FORMAT_GIB,
} from '~/lib/utils/constants';

describe('Number Utils', () => {
  describe('formatRelevantDigits', () => {
    it('returns an empty string when the number is NaN', () => {
      expect(formatRelevantDigits('fail')).toBe('');
    });

    it('returns 4 decimals when there is 4 plus digits to the left', () => {
      const formattedNumber = formatRelevantDigits('1000.1234567');
      const rightFromDecimal = formattedNumber.split('.')[1];
      const leftFromDecimal = formattedNumber.split('.')[0];

      expect(rightFromDecimal.length).toBe(4);
      expect(leftFromDecimal.length).toBe(4);
    });

    it('returns 3 decimals when there is 1 digit to the left', () => {
      const formattedNumber = formatRelevantDigits('0.1234567');
      const rightFromDecimal = formattedNumber.split('.')[1];
      const leftFromDecimal = formattedNumber.split('.')[0];

      expect(rightFromDecimal.length).toBe(3);
      expect(leftFromDecimal.length).toBe(1);
    });

    it('returns 2 decimals when there is 2 digits to the left', () => {
      const formattedNumber = formatRelevantDigits('10.1234567');
      const rightFromDecimal = formattedNumber.split('.')[1];
      const leftFromDecimal = formattedNumber.split('.')[0];

      expect(rightFromDecimal.length).toBe(2);
      expect(leftFromDecimal.length).toBe(2);
    });

    it('returns 1 decimal when there is 3 digits to the left', () => {
      const formattedNumber = formatRelevantDigits('100.1234567');
      const rightFromDecimal = formattedNumber.split('.')[1];
      const leftFromDecimal = formattedNumber.split('.')[0];

      expect(rightFromDecimal.length).toBe(1);
      expect(leftFromDecimal.length).toBe(3);
    });
  });

  describe('bytesToKiB', () => {
    it('calculates KiB for the given bytes', () => {
      expect(bytesToKiB(1024)).toEqual(1);
      expect(bytesToKiB(1000)).toEqual(0.9765625);
    });
  });

  describe('bytesToMiB', () => {
    it('calculates MiB for the given bytes', () => {
      expect(bytesToMiB(1048576)).toEqual(1);
      expect(bytesToMiB(1000000)).toEqual(0.95367431640625);
    });
  });

  describe('bytesToGiB', () => {
    it('calculates GiB for the given bytes', () => {
      expect(bytesToGiB(1073741824)).toEqual(1);
      expect(bytesToGiB(10737418240)).toEqual(10);
    });
  });

  describe('numberToHumanSizeSplit', () => {
    it('should return bytes', () => {
      expect(numberToHumanSizeSplit(654)).toEqual(['654', BYTES_FORMAT_BYTES]);
      expect(numberToHumanSizeSplit(-654)).toEqual(['-654', BYTES_FORMAT_BYTES]);
    });

    it('should return KiB', () => {
      expect(numberToHumanSizeSplit(1079)).toEqual(['1.05', BYTES_FORMAT_KIB]);
      expect(numberToHumanSizeSplit(-1079)).toEqual(['-1.05', BYTES_FORMAT_KIB]);
    });

    it('should return MiB', () => {
      expect(numberToHumanSizeSplit(10485764)).toEqual(['10.00', BYTES_FORMAT_MIB]);
      expect(numberToHumanSizeSplit(-10485764)).toEqual(['-10.00', BYTES_FORMAT_MIB]);
    });

    it('should return GiB', () => {
      expect(numberToHumanSizeSplit(10737418240)).toEqual(['10.00', BYTES_FORMAT_GIB]);
      expect(numberToHumanSizeSplit(-10737418240)).toEqual(['-10.00', BYTES_FORMAT_GIB]);
    });
  });

  describe('numberToHumanSize', () => {
    it('should return bytes', () => {
      expect(numberToHumanSize(654)).toEqual('654 bytes');
      expect(numberToHumanSize(-654)).toEqual('-654 bytes');
    });

    it('should return KiB', () => {
      expect(numberToHumanSize(1079)).toEqual('1.05 KiB');
      expect(numberToHumanSize(-1079)).toEqual('-1.05 KiB');
    });

    it('should return MiB', () => {
      expect(numberToHumanSize(10485764)).toEqual('10.00 MiB');
      expect(numberToHumanSize(-10485764)).toEqual('-10.00 MiB');
    });

    it('should return GiB', () => {
      expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB');
      expect(numberToHumanSize(-10737418240)).toEqual('-10.00 GiB');
    });
  });

  describe('numberToMetricPrefix', () => {
    it.each`
      number       | expected
      ${123}       | ${'123'}
      ${1234}      | ${'1.2k'}
      ${12345}     | ${'12.3k'}
      ${123456}    | ${'123.5k'}
      ${1234567}   | ${'1.2m'}
      ${12345678}  | ${'12.3m'}
      ${123456789} | ${'123.5m'}
    `('returns $expected given $number', ({ number, expected }) => {
      expect(numberToMetricPrefix(number)).toBe(expected);
    });
  });

  describe('sum', () => {
    it('should add up two values', () => {
      expect(sum(1, 2)).toEqual(3);
    });

    it('should add up all the values in an array when passed to a reducer', () => {
      expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15);
    });
  });

  describe('isOdd', () => {
    it('should return 0 with a even number', () => {
      expect(isOdd(2)).toEqual(0);
    });

    it('should return 1 with a odd number', () => {
      expect(isOdd(1)).toEqual(1);
    });
  });

  describe('median', () => {
    it('computes the median for a given array with odd length', () => {
      const items = [10, 27, 20, 5, 19];
      expect(median(items)).toBe(19);
    });

    it('computes the median for a given array with even length', () => {
      const items = [10, 27, 20, 5, 19, 4];
      expect(median(items)).toBe(14.5);
    });
  });

  describe('changeInPercent', () => {
    it.each`
      firstValue | secondValue | expectedOutput
      ${99}      | ${100}      | ${1}
      ${100}     | ${99}       | ${-1}
      ${0}       | ${99}       | ${Infinity}
      ${2}       | ${2}        | ${0}
      ${-100}    | ${-99}      | ${1}
    `(
      'computes the change between $firstValue and $secondValue in percent',
      ({ firstValue, secondValue, expectedOutput }) => {
        expect(changeInPercent(firstValue, secondValue)).toBe(expectedOutput);
      },
    );
  });

  describe('formattedChangeInPercent', () => {
    it('prepends "%" to the output', () => {
      expect(formattedChangeInPercent(1, 2)).toMatch(/%$/);
    });

    it('indicates if the change was a decrease', () => {
      expect(formattedChangeInPercent(100, 99)).toContain('-1');
    });

    it('indicates if the change was an increase', () => {
      expect(formattedChangeInPercent(99, 100)).toContain('+1');
    });

    it('shows "-" per default if the change can not be expressed in an integer', () => {
      expect(formattedChangeInPercent(0, 1)).toBe('-');
    });

    it('shows the given fallback if the change can not be expressed in an integer', () => {
      expect(formattedChangeInPercent(0, 1, { nonFiniteResult: '*' })).toBe('*');
    });
  });

  describe('isNumeric', () => {
    it.each`
      value        | outcome
      ${0}         | ${true}
      ${12345}     | ${true}
      ${'0'}       | ${true}
      ${'12345'}   | ${true}
      ${1.0}       | ${true}
      ${'1.0'}     | ${true}
      ${'abcd'}    | ${false}
      ${'abcd100'} | ${false}
      ${''}        | ${false}
      ${false}     | ${false}
      ${true}      | ${false}
      ${undefined} | ${false}
      ${null}      | ${false}
    `('when called with $value it returns $outcome', ({ value, outcome }) => {
      expect(isNumeric(value)).toBe(outcome);
    });
  });

  describe.each`
    value        | outcome
    ${0}         | ${true}
    ${'0'}       | ${true}
    ${12345}     | ${true}
    ${'12345'}   | ${true}
    ${-1}        | ${false}
    ${'-1'}      | ${false}
    ${1.01}      | ${false}
    ${'1.01'}    | ${false}
    ${'abcd'}    | ${false}
    ${'100abcd'} | ${false}
    ${'abcd100'} | ${false}
    ${''}        | ${false}
    ${false}     | ${false}
    ${true}      | ${false}
    ${undefined} | ${false}
    ${null}      | ${false}
    ${Infinity}  | ${false}
  `('isPositiveInteger', ({ value, outcome }) => {
    it(`when called with ${typeof value} ${value} it returns ${outcome}`, () => {
      expect(isPositiveInteger(value)).toBe(outcome);
    });
  });
});
